/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.beepmystuff;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;

import com.google.beepmystuff.NetworkService.NetworkServiceConnection;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;

/**
 * Main activity for BeepMyStuff for Android.
 *
 * Provides a simple home screen.
 */
public class HomeScreenActivity extends Activity {
  private static final String TAG = "BMS.HomeScreenActivity";
  private NetworkServiceConnection networkServiceConnection = new NetworkServiceConnection();
  private NetworkService networkService;
  private Preferences preferences;
  private BmsClient.SessionStorage bmsSessionStorage;
  private boolean showDebuggingMenu = false;

  /**
   * Whether a successful scan should initiate another scan.
   * Set before each scan.
   */
  private boolean continuousMode = false;

  /**
   * Notified to indicate that the network service has been created and the
   * activity has finished initialising.  For tests.
   * 
   * @see #waitUntilServiceFullyBound()
   */
  private Object serviceFullyBound = new Object();

  /**
   * Called when the activity is first created.
   * */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.home);

    Injector injector = Injector.getInjector(getApplication());
    preferences = injector.getPreferences();
    bmsSessionStorage = injector.getSessionStorage();

    // TODO: a) shouldn't we delegate to LoginActivity here if needed?
    // b) Make sure we never attempt to login using a blank u/p.
    // c) make LoginActivity _replace_ this activity (and remove the menu option?)
    // d) write a test to test backing out of LoginActivity after first-run??
    // e) test with Wireshark to see what our HTTP traffic looks like?

    // Establish a connection with our service (starting it if necessary).
    NetworkService.bind(this, networkServiceConnection,
        new Runnable() {
          @Override
          public void run() {
            Log.d(TAG, "Network service has bound");
            networkService = networkServiceConnection.getService();
            createAfterServiceAvailable();
            synchronized (serviceFullyBound) {
              serviceFullyBound.notify();
            }
          }
    });
  }

  /** Continue creation after the network service has been started. */
  private void createAfterServiceAvailable() {
    // Connect the scan button's click event to the item scanner.
    findViewById(R.id.beep).setOnClickListener(new OnClickListener() {
      public void onClick(View view) {
        scanItem(false);
      }
    });
    findViewById(R.id.beep_lots).setOnClickListener(new OnClickListener() {
      public void onClick(View view) {
        scanItem(true);
      }
    });

    // Wire up the recently scanned button.
    findViewById(R.id.recently_scanned).setOnClickListener(new OnClickListener() {
      public void onClick(View view) {
        startActivity(new Intent(HomeScreenActivity.this, RecentlyScannedActivity.class));
      }
    });
  }
  
  /**
   * Waits until the network service has been started and bound.
   * Called by tests to ensure that activity initialisation is complete.
   */
  public void waitUntilServiceFullyBound() throws InterruptedException {
    synchronized (serviceFullyBound) {
      while (networkService == null) {
        serviceFullyBound.wait();
      }
    }
  }

  /**
   * Called just before the activity starts interacting with the user.
   * This may be due to the settings dialog having been dismissed; so re-read
   * the network credentials from the preferences, and pass them to the network
   * service.
   */
  @Override
  public void onResume() {
    super.onResume();
    // The network service may not have started yet, in which case we can't
    // provide it with new credentials (it reads them from preferences on
    // creation anyway).
    if (networkService != null) {
      networkService.setCredentials(preferences.getNickname(), preferences.getPassword());
    }

    checkUpgrade();
    checkCredentials();
  }

  /**
   * Checks to see if the credentials we have are valid, and if not, goes to
   * the login activity to get them.
   */
  private void checkCredentials() {
    if (TextUtils.isEmpty(preferences.getNickname())
        || TextUtils.isEmpty(preferences.getPassword())
        || TextUtils.isEmpty(bmsSessionStorage.getSessionToken())) {
      // With no valid nickname, password and session token, go to the login
      // activity.
      startActivity(new Intent(this, LoginActivity.class));
    }
  }

  /**
   * Checks to see if we've been installed for the first time or have been
   * upgraded from a prior version. In future info screens explaining what's
   * new can be displayed here.
   */
  private void checkUpgrade() {
    @SuppressWarnings("unused")
    String lastVersion = preferences.getLastRunVersion();
    PackageInfo pi;
    try {
      pi = getPackageManager().getPackageInfo(getPackageName(), 0);
    } catch (NameNotFoundException e) {
      throw new AssertionError("Could not get our own package info: " + e);
    }
    preferences.setLastRunVersion(Integer.toString(pi.versionCode));
    // Any required upgrade notification performed here.
  }

  /**
   * Called before the activity is destroyed.
   */
  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy");
    // This check isn't foolproof: if the service has been recently unbound,
    // we may not have received the notification yet, but this call will still
    // fail.  Luckily, we're the only people who unbind the service, so we
    // should be okay.
    if (networkServiceConnection.getService() != null) {
      unbindService(networkServiceConnection);
    }
  }

  /**
   * Create the activity's options menu.
   */
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.options_menu, menu);

    menu.findItem(R.id.menu_login)
        .setIntent(new Intent(this, LoginActivity.class));
    menu.findItem(R.id.menu_settings)
        .setIntent(new Intent(this, SettingsActivity.class));
    menu.findItem(R.id.menu_manual)
        .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
              @Override
              public boolean onMenuItemClick(MenuItem item) {
                startActivityForResult(new Intent(HomeScreenActivity.this, ManualEntryActivity.class),
                    IntentIntegrator.REQUEST_CODE);
                return true;
              }
            });

    menu.findItem(R.id.menu_debug_clear_prefs)
        .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
              @Override
              public boolean onMenuItemClick(MenuItem item) {
                debugClearPrefs();
                return true;
              }
            });
    menu.findItem(R.id.menu_debug_fake_scan)
        .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
              @Override
              public boolean onMenuItemClick(MenuItem item) {
                debugFakeScan();
                return true;
              }
            });

    return true;
  }

  /**
   * Start a scan by asking zxing to scan an item. It will return to us via
   * onActivityResult below.
   * @see #onActivityResult(int, int, Intent)
   */
  private void scanItem(boolean continuousMode) {
    // TODO: prior to moving to zxing's own IntentIntegrator, I used to set
    // zxing to only look for products. Would be nice to update
    // IntentIntegrator to give the same functionality.
    this.continuousMode = continuousMode;
    IntentIntegrator.initiateScan(this);
  }

  /**
   * Called when an external activity completes, returning a value to us.
   * This is used after we've shelled out to zxing to ask it to scan a barcode
   * for us.
   */
  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent intent) {
    IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
    if (result != null && !TextUtils.isEmpty(result.getContents())) {
      String contents = result.getContents();
      String format = result.getFormatName();
      Log.i(TAG, "Scanned " + format + " barcode: " + contents);
      networkService.getScanRegistry().addEan(contents, preferences.getShareWith());
      networkService.wakeUp();
      if (continuousMode) {
        Log.d(TAG, "Multiple mode - rescanning");
        scanItem(true);
      }
    }
  }

  /** Optionally makes debugging menu visible. */
  @Override
  public boolean onPrepareOptionsMenu(Menu menu) {
    menu.setGroupVisible(R.id.menu_debug_group, showDebuggingMenu);
    return super.onPrepareOptionsMenu(menu);
  }

  /**
   * Debug menu option: clears preferences and shuts down.
   * Does nothing if we're running with an injected preferences object.
   */
  private void debugClearPrefs() {
    if (preferences instanceof SharedPreferences) {
      ((SharedPreferences) preferences).clear();
      // This doesn't exit the process: it seems to be waiting for something.
      // TODO: investigate whether we're leaking something.
      finish();
    }
  }

  /**
   * Debug menu option: Fakes a scan, so that it's possible to test
   * manually without installing Barcode Scanner (which is a pain if there's
   * also no Market installed).
   */
  private void debugFakeScan() {
    onActivityResult(IntentIntegrator.REQUEST_CODE, RESULT_OK,
        new Intent().putExtra("SCAN_RESULT", "9780201038095")
            .putExtra("SCAN_RESULT_FORMAT", "EAN_13"));
  }

  /** Toggle the debugging menu when the secret debugging key is pressed. */
  @Override
  public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_1) {
      showDebuggingMenu = !showDebuggingMenu;
      return true;
    }
    return super.onKeyDown(keyCode, event);
  }
}
